package com.example.demo02.config;


import com.example.demo02.service.TUserService;
import com.example.demo02.utils.MyAccessDecisionManager;
import com.example.demo02.utils.MyFilterInvocationSecurityMetadataSource;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.PrintWriter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig{
    @Resource
    ValidCodeFilter validCodeFilter;
    @Resource
    TUserService tUserService;
    @Resource
    MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
    @Resource
    MyAccessDecisionManager myAccessDecisionManager;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
        //如果不想加密就返回
        //return NoOpPasswordEncoder.getInstance();
    }

    //如果用数据库则不用创建内存数据
//    @Bean
//    public UserDetailsService userDetailsService() {
//        // 1.使用内存数据进行认证
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//        // 2.创建4个用户
//        ////设置内存用户名与密码,并赋与角色
//        UserDetails user1 = User.withUsername("admin").password(passwordEncoder().encode("123")).roles("role1","role2","role3").build();
//        UserDetails user2 = User.withUsername("user1").password(passwordEncoder().encode("123")).roles("role1").build();
//        UserDetails user3 = User.withUsername("user2").password(passwordEncoder().encode("123")).roles("role2").build();
//        UserDetails user4 = User.withUsername("user3").password(passwordEncoder().encode("123")).roles("role3").build();
//
//        // 3.将这4个用户添加到内存中
//        manager.createUser(user1);
//        manager.createUser(user2);
//        manager.createUser(user3);
//        manager.createUser(user4);
//        return manager;
//    }

    //静态资源直接放行
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        //忽略这些静态资源（不拦截）
        return (web) -> web.ignoring().requestMatchers("/js/**", "/css/**","/images/**");
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //在认证用户名与密码之前加validCodeFilter过滤器，将先验证验证码 验证码通过才验证用户名与密码
        httpSecurity.addFilterBefore(validCodeFilter, UsernamePasswordAuthenticationFilter.class);

        httpSecurity.authorizeHttpRequests() //开启登录配置
                //允许直接访问的路径
                .requestMatchers("/","/index","/validCode").permitAll()
                //用户需要有role1的角色才能访问/menu1/** 下面是进阶写法
//                .requestMatchers("/menu1/**").hasRole("user")
//                .requestMatchers("/menu2/**").hasRole("manager")
//                .requestMatchers("/menu3/**").hasRole("admin")
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        object.setAccessDecisionManager(myAccessDecisionManager);
                        return object;
                    }
                })
                .anyRequest().authenticated();//其他任何请求都必须经过身份验证
        //当没有权限时跳转403页面
        //httpSecurity.exceptionHandling().accessDeniedPage("/errorRole");
        //前后端分离项目，无权限时可以不指定跳转页，而是返回 JSON 信息
        httpSecurity.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            String json = "{\"status\":\"error\",\"msg\":\"权限不足\"}";
            out.write(json);
        });


        httpSecurity.formLogin()//开启表单验证
                .loginPage("/toLogin") //跳转到自定义的登录页面
                .usernameParameter("username") //自定义表单的用户名的name属性 默认为username
                .passwordParameter("password") //自定义表单的密码的name属性 默认为password
                .loginProcessingUrl("/login") //表单请求的地址 使用Security定义好的/login，并与自定义表单中的action一致

                //前后端分离登录成功 不需要由后端指定跳转页面，而只需返回JSON 数据即可
                .successHandler((request, response, authentication) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    String json = "{\"status\":\"ok\",\"msg\":\"登录成功\"}";
                    out.write(json);
                })

                //如果登陆失败跳转到 如果是前后端分离登录就不需要这种方式
                //.failureUrl("/toLogin/error")

                //前后端分离登录失败 不需要由后端指定跳转页面，而只需返回JSON 数据即可
                .failureHandler((request, response, exception) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    String json = "{\"status\":\"error\",\"msg\":\"" + exception.getMessage() + "\"}";
                    out.write(json);
                })

                .permitAll();// 允许访问登录有关的路径

        //登录后注销后所跳转到的index页面 如果是前后端分离退出登录就不需要这种方式
        //httpSecurity.logout().logoutSuccessUrl("/index");

        //前后端分离退出登录 登录后能注销登录
        httpSecurity.logout().logoutSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            String json = "{\"status\":\"ok\",\"msg\":\"退出登录\"}";
            out.write(json);
        });
        httpSecurity.csrf().disable();//关闭csrf 如果不关闭 csrf，则注销不会成功
        httpSecurity.rememberMe(); //记住我
        httpSecurity.userDetailsService(tUserService);
        return httpSecurity.build();
    }


}
